package com.autumn.mybatis.metadata;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import com.autumn.mybatis.mapper.ISpecificationDefinition;
import com.autumn.mybatis.mapper.MapperException;
import com.autumn.mybatis.mapper.PropertyMapperUtils;
import com.autumn.mybatis.mapper.annotation.ColumnOrder;
import com.autumn.util.StringUtils;
import com.autumn.util.TypeUtils;
import com.autumn.util.reflect.BeanProperty;
import com.autumn.util.reflect.ReflectUtils;
import com.autumn.util.tuple.TupleTwo;
import com.esotericsoftware.reflectasm.ConstructorAccess;

/**
 * 实体列
 * 
 * @author 杨昌国
 *
 *         2017-09-27 17:17:46
 */
public class EntityColumn implements Comparable<EntityColumn>, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 5192599894162062927L;
	private final EntityTable table;
	private final BeanProperty property;
	private final String propertyName;
	private final String columnName;
	private final Class<?> javaType;
	private final JdbcType jdbcType;
	private final Class<? extends TypeHandler<?>> typeHandlerClass;
	private final boolean isPrimaryKey;
	private final boolean insertable;
	private final boolean updatable;
	private final boolean nullable;
	private final int columnOrder;
	private final int length;
	private final int precision;
	private final String sequenceName;
	private final int scale;
	private final String orderBy;
	private final GenerationType generationType;
	private final ConstructorAccess<? extends TypeHandler<?>> typeHandlerConstructorAccess;
	private final String columnHolder;

	/**
	 * 实例化 EntityColumn 类
	 * 
	 * @param table
	 *            表
	 * @param property
	 *            属性
	 * @param specificationDefinition
	 *            规范
	 */
	public EntityColumn(EntityTable table, BeanProperty property, ISpecificationDefinition specificationDefinition)
			throws MapperException {
		if (!TypeUtils.isBaseOrBinaryType(property.getType())) {
			throw new MapperException("不支持 " + property.getType().getName() + " 类型映射。");
		}
		if (specificationDefinition == null) {
			specificationDefinition = ISpecificationDefinition.DEFAULT_DEFINITION;
		}
		this.table = table;
		this.property = property;
		this.javaType = property.getType();
		this.propertyName = this.property.getName();
		Column column = PropertyMapperUtils.getOrDefaultColumn(property, specificationDefinition);
		this.columnName = !StringUtils.isNullOrBlank(column.name()) ? column.name()
				: specificationDefinition.defaultColumnName(this.propertyName);
		this.insertable = column.insertable();
		this.updatable = column.updatable();
		this.length = this.javaType.equals(String.class) ? column.length() : 0;
		this.precision = column.precision();
		this.scale = column.scale();
		this.isPrimaryKey = property.isAnnotation(Id.class);
		ColumnOrder annColumnOrder = property.getAnnotation(ColumnOrder.class);
		if (annColumnOrder != null) {
			this.columnOrder = annColumnOrder.value();
		} else {
			this.columnOrder = 0;
		}
		if (this.isPrimaryKey) {
			this.nullable = false;
		} else {
			this.nullable = column.nullable();
		}
		this.sequenceName = this.isPrimaryKey && TypeUtils.isIntegerType(this.javaType)
				? PropertyMapperUtils.getColumnSequenceName(property)
				: "";
		this.generationType = this.isPrimaryKey && TypeUtils.isIntegerType(this.javaType)
				? PropertyMapperUtils.getColumnGenerationType(property)
				: GenerationType.AUTO;
		if (this.generationType.equals(GenerationType.SEQUENCE)) {
			if (StringUtils.isNullOrBlank(this.sequenceName)) {
				throw new MapperException("主键生成方式为序列(SEQUENCE)时 sequenceName 的值不能为空或空白值。");
			}
		}
		TupleTwo<JdbcType, Class<? extends TypeHandler<?>>> typeMap = PropertyMapperUtils.getPropertyTypeMap(property);
		this.jdbcType = typeMap.getItem1();
		this.typeHandlerClass = typeMap.getItem2();
		this.typeHandlerConstructorAccess = ReflectUtils.getConstructorAccess(this.typeHandlerClass);
		this.orderBy = PropertyMapperUtils.getColumnOrderBy(property);
		this.columnHolder = getColumnHolder(false, null);
	}

	/**
	 * 获取属性
	 * 
	 * @return
	 * @author 杨昌国 2017-09-29 10:05:50
	 */
	public BeanProperty getProperty() {
		return property;
	}

	/**
	 * 获取对应的表
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:50:36
	 */
	public EntityTable getTable() {
		return table;
	}

	/**
	 * 获取Java类型
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:50:47
	 */
	public Class<?> getJavaType() {
		return javaType;
	}

	/**
	 * 获取Jdbc类型
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:51:12
	 */
	public JdbcType getJdbcType() {
		return jdbcType;
	}

	/**
	 * 获取类型处理器类
	 * 
	 * @return
	 */
	public Class<? extends TypeHandler<?>> getTypeHandlerClass() {
		return typeHandlerClass;
	}

	/**
	 * 创建类型处理器实例
	 * 
	 * @return
	 */
	public TypeHandler<?> newTypeHandlerInstance() {
		return this.typeHandlerConstructorAccess.newInstance();
	}

	/**
	 * 获取是否是主键
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:51:22
	 */
	public boolean isPrimaryKey() {
		return isPrimaryKey;
	}

	/**
	 * 获取是否插入
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:51:32
	 */
	public boolean isInsertable() {
		return insertable;
	}

	/**
	 * 获取是否更新
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:51:46
	 */
	public boolean isUpdatable() {
		return updatable;
	}

	/**
	 * 获取允许为空
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:51:54
	 */
	public boolean isNullable() {
		return nullable;
	}

	/**
	 * 获取长度
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:52:43
	 */
	public int getLength() {
		return length;
	}

	/**
	 * 获取精度
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:52:51
	 */
	public int getPrecision() {
		return precision;
	}

	/**
	 * 获取小数位数
	 * 
	 * @return
	 * @author 杨昌国 2017-09-28 09:54:28
	 */
	public int getScale() {
		return scale;
	}

	/**
	 * 获取属性名称
	 * 
	 * @return
	 * @author 杨昌国 2017-09-27 18:18:28
	 */
	public String getPropertyName() {
		return propertyName;
	}

	/**
	 * 获取列名称
	 * 
	 * @return
	 * @author 杨昌国 2017-09-27 18:18:35
	 */
	public String getColumnName() {
		return columnName;
	}

	/**
	 * 获取排序方式
	 * 
	 * @return
	 */
	public String getOrderBy() {
		return orderBy;
	}

	/**
	 * 获取列顺序
	 * 
	 * @return
	 *
	 */
	public final int getColumnOrder() {
		return this.columnOrder;
	}

	/**
	 * 获取生成方式(包含非主键)
	 * 
	 * @return
	 */
	public GenerationType getGenerationType() {
		return generationType;
	}

	/**
	 * 是否为自动生成
	 * 
	 * @return
	 */
	public boolean isIdentity() {
		return GenerationType.IDENTITY.equals(this.getGenerationType());
	}

	/**
	 * 是否是主键并且自动生成
	 * 
	 * @return
	 */
	public boolean isPrimaryKeyIdentity() {
		return this.isPrimaryKey() && isIdentity();
	}

	/**
	 * 获取序列名称
	 * 
	 * @return
	 */
	public String getSequenceName() {
		return sequenceName;
	}

	/**
	 * 获取值
	 * 
	 * @param instance
	 *            实例
	 * @return
	 * @author 杨昌国 2017-09-29 10:00:35
	 */
	public Object getValue(Object instance) {
		return property.getValue(instance);
	}

	/**
	 * 设置值
	 * 
	 * @param instance
	 *            实例
	 * @param value
	 *            值
	 * @author 杨昌国 2017-09-29 10:01:46
	 */
	public void setValue(Object instance, Object value) {
		property.setValue(instance, TypeUtils.toObjectConvert(this.getJavaType(), value));
	}

	/**
	 * 获取列的处理器(逗号分隔）
	 *
	 * @param isContainEntityClass
	 *            包括实体类
	 * @param prefixes
	 *            前缀
	 * @return 返回格式如:#{entityName.age+prefixes,jdbcType=NUMERIC,typeHandler=MyTypeHandler},
	 */
	public String getColumnHolderWithComma(boolean isContainEntityClass, String prefixes) {
		return getColumnHolder(isContainEntityClass, prefixes, ",");
	}

	/**
	 * 获取列的处理器(默认)
	 * 
	 * @return 返回格式如:#{entityName.age+prefixes,jdbcType=NUMERIC,typeHandler=MyTypeHandler},
	 */
	public String getColumnHolder() {
		return this.columnHolder;
	}

	/**
	 * 获取列的处理器(无分隔）
	 *
	 * @param isContainEntityClass
	 *            包括实体类
	 * @param prefixes
	 *            前缀
	 * @return 返回格式如:#{entityName.age+prefixes,jdbcType=NUMERIC,typeHandler=MyTypeHandler},
	 */
	public String getColumnHolder(boolean isContainEntityClass, String prefixes) {
		return getColumnHolder(isContainEntityClass, prefixes, null);
	}

	/**
	 * 获取列的处理器
	 *
	 * @param isContainEntityClass
	 *            包括实体类
	 * @param prefixes
	 *            前缀
	 * @param separator
	 *            分隔符
	 * @return 返回格式如:#{entityName.age+prefixes,jdbcType=NUMERIC,typeHandler=MyTypeHandler}+separator
	 */
	public String getColumnHolder(boolean isContainEntityClass, String prefixes, String separator) {
		StringBuffer sb = new StringBuffer("#{");
		if (isContainEntityClass) {
			sb.append(this.getTable().getEntityClass().getName());
			sb.append(".");
		}
		if (!StringUtils.isNullOrBlank(prefixes)) {
			sb.append(prefixes);
		}
		sb.append(this.getPropertyName());

		sb.append(",jdbcType=");
		sb.append(this.jdbcType.toString());
		sb.append(",typeHandler=");
		sb.append(this.typeHandlerClass.getCanonicalName());

		sb.append("}");
		if (!StringUtils.isNullOrBlank(separator)) {
			sb.append(separator);
		}
		return sb.toString();
	}

	/**
	 * 获取列操作符处理器
	 * 
	 * @param operator
	 *            操作符 =、>=、<=、<>等
	 * @return
	 */
	public String getColumnOperatorHolder(String operator) {
		return String.format("%s %s %s", this.getColumnName(), operator, this.getColumnHolder());
	}

	/**
	 * 获取列等于处理器
	 * 
	 * @return
	 */
	public String getColumnEqualsHolder() {
		return getColumnOperatorHolder("=");
	}

	/**
	 * 获取列不等于处理器
	 * 
	 * @return
	 */
	public String getColumnNotEqualsHolder() {
		return getColumnOperatorHolder("<>");
	}

	/**
	 * 获取列大于处理器
	 * 
	 * @return
	 */
	public String getColumnGreaterThanHolder() {
		return getColumnOperatorHolder(">");
	}

	/**
	 * 获取列大于处理器
	 * 
	 * @return
	 */
	public String getColumnGreaterThanOrEqualsHolder() {
		return getColumnOperatorHolder(">=");
	}

	/**
	 * 获取列小于处理器
	 * 
	 * @return
	 */
	public String getColumnLessThanHolder() {
		return getColumnOperatorHolder("<");
	}

	/**
	 * 获取列小于或等于处理器
	 * 
	 * @return
	 */
	public String getColumnLessThanOrEqualsHolder() {
		return getColumnOperatorHolder("<=");
	}

	@Override
	public String toString() {
		if (this.getPropertyName().equalsIgnoreCase(this.getColumnName())) {
			return String.format("%s(%s -> %s)", this.getPropertyName(), this.getJavaType().getName(),
					this.getJdbcType());
		}
		return String.format("%s[%s](%s -> %s)", this.getPropertyName(), this.getColumnName(),
				this.getJavaType().getName(), this.getJdbcType());
	}

	@Override
	public int compareTo(EntityColumn o) {
		int value = this.getColumnOrder() - o.getColumnOrder();
		if (value > 0) {
			return 1;
		} else if (value < 0) {
			return -1;
		}
		return value;
	}

}
